-- don't load if class is wrong
local _, class = UnitClass("player")
if class ~= "PALADIN" then return end

local GetTime = GetTime
local debug = clcInfo.debug

local version = 2

local modName = "__protection"
local mod = clcInfo:RegisterClassModule(modName)
local emod = clcInfo.env
local db -- template based

local ef = CreateFrame("Frame") 
ef:Hide()

local defaults = {
	version = version,
	
	prio = "sotr cs j as how cons hw",
	rangePerSkill = false,
}

-- @defines
--------------------------------------------------------------------------------
local gcdId 				= 85256 -- tv for gcd
-- list of spellId
local howId 				=	24275	-- hammer of wrath
local csId 					= 35395	-- crusader strike
local asId					= 31935	-- avenger's shield
local hotrId				= 53595	-- hammer of the righteous
local sotrId				= 53600	-- shield of the righteous
local inqId					= 84963	-- inquisition
local jId						= 20271	-- judgement
local consId				= 26573	-- consecration
local hwId					= 2812 	-- holy wrath
-- csName to pass as argument for melee range checks
local csName			= GetSpellInfo(csId)
-- buffs
local buffInq 	= GetSpellInfo(inqId)		-- inquisition
local buffSD		= GetSpellInfo(85433)		-- sacred duty
local buffGC		= GetSpellInfo(98057)		-- grand crusader


-- status vars
local s1, s2
local s_ctime, s_otime, s_gcd, s_hp, s_inq, s_sd, s_gc, s_haste

function mod:OnTemplatesUpdate()
	db = clcInfo:RegisterClassModuleTDB(modName, defaults)
	if db then
		if not db.version or db.version < version then
			-- fix stuff
			clcInfo.AdaptConfigAndClean(modName, db, defaults)
			db.version = version
		end
		
		mod:UpdateQueue()
	end
end


-- the queue
local q = {} 			-- queue


local function GetCooldown(id)
	local start, duration = GetSpellCooldown(id)
	local cd = start + duration - s_ctime - s_gcd
	if cd < 0 then return 0 end
	return cd
end


-- actions ---------------------------------------------------------------------
local actions = {
	cs = {
		id = csId,
		GetCD = function()
			if s1 == csId then
				return 1.5
			else
				return max(0, GetCooldown(csId) - 0.3)
			end
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5
			s_hp = s_hp + 1
		end,
		info = "Crusader Strike",
	},
	hotr = {
		id = hotrId,
		GetCD = function()
			if s1 == hotrId then
				return 1.5
			else
				return max(0, GetCooldown(hotrId) - 0.3)
			end
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = s_hp + 1
		end,
		info = "Hammer of the Righteous",
	},
	sotr = {
		id = sotrId,
		GetCD = function()
			if s_hp >= 3 then
				return 0
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = 0
		end,
		info = "Shield of the Righteous",
	},
	isotr = {
		id = sotrId,
		GetCD = function()
			if s_inq <= 0 then return 100 end
			if s_hp >= 3 then
				return 0
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = 0
		end,
		info = "Shield of the Righteous (Inquisition active)",
	},
	sdsotr = {
		id = sotrId,
		GetCD = function()
			if s_sd <= 0 then return 100 end
			if s_hp >= 3 then
				return 0
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = 0
			s_sd = 0
		end,
		info = "Shield of the Righteous (Sacred Duty active)",
	},
	as = {
		id = asId,
		GetCD = function()
			if s1 ~= asId then
				return GetCooldown(asId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			if s_gc > 0 then s_hp = s_hp + 1 end
		end,
		info = "Avenger's Shield",
	},
	gcas = {
		id = asId,
		GetCD = function()
			if s_gc <= 0 then return end
			if s1 ~= asId then
				return GetCooldown(asId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = s_hp + 1
			s_gc = 0
		end,
		info = "Avenger's Shield (Grand Crusader active)",
	},
	j = {
		id = jId,
		GetCD = function()
			if s1 ~= jId then
				return GetCooldown(jId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5
		end,
		info = "Judgement",
	},
	nosdj = {
		id = jId,
		GetCD = function()
			if s_sd > 0 then return 100 end
			if s1 ~= jId then
				return GetCooldown(jId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5
		end,
		info = "Judgement (Sacred Duty not active)",
	},
	inq = {
		id = inqId,
		GetCD = function()
			if s_hp >= 3 then
				return 0
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
			s_hp = 0
			s_inq = 10 -- temp value will get fixed by next call
		end,
		info = "Inquisition",
	},
	how = {
		id = howId,
		GetCD = function()
			if IsUsableSpell(howId) and s1 ~= howId then
				return GetCooldown(howId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
		end,
		info = "Hammer of Wrath",
	},
	hw = {
		id = hwId,
		GetCD = function()
			if s1 ~= hwId then
				return GetCooldown(hwId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
		end,
		info = "Holy Wrath",
	},
	cons = {
		id = consId,
		GetCD = function()
			if s1 ~= consId then
				return GetCooldown(consId)
			end
			return 100
		end,
		UpdateStatus = function()
			s_ctime = s_ctime + s_gcd + 1.5 / s_haste
		end,
		info = "Consecration",
	},
}
mod.actions = actions
--------------------------------------------------------------------------------


function mod:UpdateQueue()
	-- normal queue
	q = {}
	for v in string.gmatch(db.prio, "[^ ]+") do
		if actions[v] then
			table.insert(q, v)
		else
			print("clcInfo", modName, "invalid action:", v)
		end
	end
	db.prio = table.concat(q, " ")
end


local function GetBuff(buff)
	local left
	_, _, _, _, _, _, expires = UnitBuff("player", buff, nil, "PLAYER")
	if expires then
		left = expires - s_ctime - s_gcd
		if left < 0 then left = 0 end
	else
		left = 0
	end
	return left
end

-- reads all the interesting data
local function GetStatus()
	-- current time
	s_ctime = GetTime()
	
	-- gcd value
	local start, duration = GetSpellCooldown(gcdId)
	s_gcd = start + duration - s_ctime
	if s_gcd < 0 then s_gcd = 0 end
	
	-- the buffs
	s_inq = GetBuff(buffInq)
	s_sd	= GetBuff(buffSD)
	s_gc	= GetBuff(buffGC)
	
	-- client hp and haste
	s_hp = UnitPower("player", SPELL_POWER_HOLY_POWER)
	s_haste = 1 + UnitSpellHaste("player") / 100
end

local function GetNextAction()
	local n = #q
	
	-- parse once, get cooldowns, return first 0
	for i = 1, n do
		local action = actions[q[i]]
		local cd = action.GetCD()
		if debug.enabled then
			debug:AddBoth(q[i], cd)
		end
		if cd == 0 then
			return action.id, q[i]
		end
		action.cd = cd
	end
	
	-- parse again, return min cooldown
	local minQ = 1
	local minCd = actions[q[1]].cd
	for i = 2, n do
		local action = actions[q[i]]
		if minCd > action.cd then
			minCd = action.cd
			minQ = i
		end
	end
	return actions[q[minQ]].id, q[minQ]
end

local function ProtRotation()
	s1 = nil
	GetStatus()
	if debug.enabled then
		debug:Clear()
		debug:AddBoth("ctime", s_ctime)
		debug:AddBoth("gcd", s_gcd)
		debug:AddBoth("hp", s_hp)
		debug:AddBoth("sd", s_sd)
		debug:AddBoth("gc", s_gc)
		debug:AddBoth("inq", s_inq)
		debug:AddBoth("haste", s_haste)
	end
	local action
	s1, action = GetNextAction()
	if debug.enabled then
		debug:AddBoth("s1", action)
	end
	-- 
	s_otime = s_ctime -- save it so we adjust buffs for next
	actions[action].UpdateStatus()
	
	-- adjust buffs
	s_otime = s_ctime - s_otime
	s_inq = max(0, s_inq - s_otime)
	s_sd = max (0, s_sd - s_otime)
	s_gc = max (0, s_gc - s_otime)
	
	if debug.enabled then
		debug:AddBoth("ctime", s_ctime)
		debug:AddBoth("otime", s_otime)
		debug:AddBoth("gcd", s_gcd)
		debug:AddBoth("hp", s_hp)
		debug:AddBoth("sd", s_sd)
		debug:AddBoth("gc", s_gc)
		debug:AddBoth("inq", s_inq)
		debug:AddBoth("haste", s_haste)
	end
	s2, action = GetNextAction()
	if debug.enabled then
		debug:AddBoth("s2", action)
	end
end


-- plug in
--------------------------------------------------------------------------------
local secondarySkill
function emod.IconProtection1(...)
	ProtRotation(...)
	if secondarySkill then secondarySkill:DoUpdate() end
	return emod.IconSpell(s1, db.rangePerSkill or csName)
end
local function SecondaryExec()
	return emod.IconSpell(s2, db.rangePerSkill or csName)
end
local function ExecCleanup2()
	secondarySkill = nil
end
function emod.IconProtection2(...)
	secondarySkill = emod.___e
	secondarySkill:SetScript("OnUpdate", nil)
	secondarySkill.exec = SecondaryExec
	secondarySkill.ExecCleanup = ExecCleanup2
end

-- giving queue as command line
local function CmdProt(args)
	db.prio = table.concat(args, " ")
	mod:UpdateQueue()
	clcInfo:UpdateOptions()
end
clcInfo.cmdList["protprio"] = CmdProt
